const debug = require('debug')('moss');
const { isString, isArray } = require('lodash');

class Moss {
    constructor(knex, options) {
        this.knex = knex;
        this.options = {
            debug: true,
            tablePrefix: '',
            idAttribute: 'id',
            listReturnKey: {
                list: 'list',
                total: 'total',
                page: 'page',
                size: 'pageSize',
                maxPage: 'maxPage',
            },
            ...options
        }

        this.init();
    }

    init() {
        this.destroy = this.delete;

        ['increment'].map(key => {
            this[key] = (options) => {
                const { table } = this._getDefaultOptions(options);
                const query = this.knex(table);

                this.runKenx(query, options)

                return query;
            }
        })
    }

    _getDefaultOptions(options) {
        if (options.debug == undefined) options.debug = this.options.debug;
        if (options.idAttribute == undefined) options.idAttribute = this.options.idAttribute;

        const { name } = options;

        options.table = name ? this.options.tablePrefix + '_' + name : options.table;

        if (!options.table) throw new Error('table 不能为空');

        return options
    }

    debug(...args) {
        args.unshift('[moss]');

        console.log(...args);
    }


    async find(options) {
        options = this._getDefaultOptions(options)

        const { table, notNull, } = options;

        const query = this.knex(table);

        this.runKenx(query, options)

        return query.then((v) => {

            if (notNull && v && v.length == 0) {
                throw isString(notNull) ? notNull : '数据不存在';
            }

            return v;
        })
    }


    async findOne(options) {
        options.limit = 1;
        const query = await this.find(options);

        return query ? query[0] : null;
    }

    async findById(id, options = {}) {
        const { idAttribute } = this._getDefaultOptions(options);

        if (!options.where) options.where = {
            [idAttribute]: id,
        }

        return this.findOne(options)
    }


    async update(options) {
        const { table, data, } = this._getDefaultOptions(options)

        const query = this.knex(table)

        this.runKenx(query, options)

        query.update(data).then(v => {
            return v;
        })

        return query;
    }

    async count(options) {
        const { table, count } = this._getDefaultOptions(options)

        const column = options.column || '*';

        const query = this.knex(table)

        this.runKenx(query, options)

        return query.count(count || `${column} as c`).then(countValue => {
            if (count) {
                return countValue;
            } else {
                return countValue[0].c;
            }
        })
    }

    /**
     * 更新插入
     * @param {*} options 
     */
    async upsert(options) {

        const result = await this.findOne(options)

        if (result) {
            return this.update(options)
        } else {
            return this.create(options)
        }
    }

    create(options) {

        const { table, data, rawData, idAttribute } = this._getDefaultOptions(options);

        const query = this.knex(table)

        this.runKenx(query, options)

        query.insert(data);

        return query.then((v) => {
            if (rawData) {
                return this.findOne({
                    table,
                    where: options.where || {
                        [idAttribute]: v[0],
                    }
                })
            }

            return v;
        })
    }


    delete(options) {
        const { table } = this._getDefaultOptions(options);

        const query = this.knex(table);

        this.runKenx(query, options)

        query.del();

        return query
    }

    /**
     * 执行Knex中的函数
     * @param {*} query 
     * @param {*} param1 
     */
    runKenx(query, options = {}) {

        // const filter = ['table', 'notNull', 'debug', 'name', 'idAttribute', 'data', 'rawData', 'page', 'size'];

        const { debug } = options;

        Object.keys(options).map((command) => {
            // 过滤Key
            // if (filter && filter.includes(command)) return;

            let isApply
            if (command.indexOf('$') == 0) {
                command = command.substring(1);
                isApply = true;
            }

            if (this.knex[command]) {
                const value = options[command];
                // $ 开头代表特殊语法、传入的数据转换为数组作为函数参数传递
                // $orderBy: ['id', 'desc'] == orderBy('id','desc)
                try {

                    if (isApply) {
                        query[command].apply(query, isArray(value) ? value : [value])
                    } else {
                        query[command](value);
                    }
                } catch (e) {
                    throw console.error('无效字段：' + command, e);;
                }
            }

        })


        if (debug) {
            const debugData = options;

            debugData.sql = query.toString();
            this.debug(debugData);
        }

        return query;
    }


    async list(options = {}, keys = {}) {
        options = {
            ...options
        }

        keys = {
            ...this.options.listReturnKey,
            ...keys,
        }

        let page = Number(options[keys.page] || 1);
        let size = Number(options[keys.size] || 20);
        // if (page < 1) page = 1;

        // 获取列表
        const getContent = this.find({
            limit: size,
            offset: (page - 1) * size,
            ...options,
        })

        delete options.page;
        delete options.size;

        // 获取总数
        const countQuery = {
            count: '* as total',
            ...options,
        }
        delete countQuery.limit;
        delete countQuery.offset;

        const getTotal = this.count(countQuery);

        try {
            const [contentRet, totalRet] = await Promise.all([getContent, getTotal]);
            const total = totalRet && totalRet.length ? totalRet[0].total : 0;
            let maxPage = parseInt(total / size) || 1;
            if (maxPage == 0 || parseInt(total % size)) {
                maxPage++;
            }
            const data = {
                [keys.list]: contentRet,
                [keys.total]: total,
                [keys.page]: page,
                [keys.size]: size,
                [keys.maxPage]: maxPage,
            }

            return data;
        } catch (e) {
            return Promise.reject(['语法异常', e]);
        }


    }

}

module.exports = Moss;